package org.naxx.security.api.impl;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.InputStream;
import java.io.Writer;
import java.util.Set;

import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;

import org.apache.log4j.Logger;
import org.naxx.security.api.NaxxRepositoryManager;
import org.naxx.security.compiler.XacmlCompiler;
import org.naxx.security.model.Policy;
import org.naxx.security.model.Rule;
import org.naxx.security.parser.PolicyParser;
import org.naxx.security.parser.validation.SchemaValidator;
import org.naxx.security.util.Assert;
import org.naxx.security.util.FileSystemUtil;
import org.naxx.security.xgen.parts.XConditionGenerator;
import org.naxx.security.xgen.util.JavaNameGenerator;
import org.naxx.security.xgen.util.XWriter;

/**
 * Handles policy lifecycle events and naxxlets management. For now
 * only condition processing has been implemented.
 */
public class NaxxRepositoryManagerImpl implements NaxxRepositoryManager {

	private static final Logger logger = Logger
			.getLogger(NaxxRepositoryManagerImpl.class);

	private PolicyParser policyParser;
	private SchemaValidator schemaValidator;
	private XConditionGenerator xConditiongGenerator;
	private XacmlCompiler compiler;
	private JavaNameGenerator javaNameGenerator;
	private String naxxletsFolder;
	
	public void setPolicyParser(PolicyParser policyParser) {
		this.policyParser = policyParser;
	}
	public void setSchemaValidator(SchemaValidator schemaValidator) {
		this.schemaValidator = schemaValidator;
	}
	public void setConditionGenerator(XConditionGenerator conditiongGenerator) {
		xConditiongGenerator = conditiongGenerator;
	}
	public void setCompiler(XacmlCompiler compiler) {
		this.compiler = compiler;
	}
	public void setJavaNameGenerator(JavaNameGenerator javaNameGenerator) {
		this.javaNameGenerator = javaNameGenerator;
	}
	public void setNaxxletsFolder(String naxxletsFolder) {
		this.naxxletsFolder = naxxletsFolder;
	}
	
	/**
	 * For now policy names should be valid folder names.
	 */
	@Override
	public void process(final String policyFileName) throws Exception {
		this.validateAgainstSchema(policyFileName);
		logger.info("Policy file '" + policyFileName
				+ "' is valid according to XACML schema");
		XMLEventReader reader = null;
		InputStream policyInputStream = null;
		Policy policy = null;
		try {
			policyInputStream = new FileInputStream(policyFileName);
			reader = XMLInputFactory.newInstance().createXMLEventReader(
					policyInputStream);
			this.policyParser.skipDocumentStart(reader);
			policy = this.policyParser.parse(reader);
		} finally {
			FileSystemUtil.release(reader);
			FileSystemUtil.close(policyInputStream);
		}
		final String policyName = policy.getId();
		logger.info("Policy '" + policyName + "' successfully parsed");

		Set<Rule> rules = policy.getRules();
		logger.info("Found '" + (rules == null ? 0 : rules.size())
				+ "' rules in policy '" + policyName + "'");
		
		final String policyNaxxletFolder = this.naxxletsFolder + File.separatorChar
				+ policyName;
		FileSystemUtil.createFolder(policyNaxxletFolder);
		logger.info("Successfully create folder '" + policyNaxxletFolder+ "'.");
		for (Rule rule : rules) {
			String generatedClassName = "Condition" + this.javaNameGenerator.getNextUniqueClassNameSuffix();
			Writer writer = null;
			try {
				writer = new FileWriter(policyNaxxletFolder + File.separatorChar + generatedClassName + ".java");
				this.xConditiongGenerator.generateConditionClass(new XWriter(writer), rule
						.getCondition(), generatedClassName);
			} finally {
				FileSystemUtil.close(writer);
			}
			logger.info("Successfully generated class '" + generatedClassName + "'.");
		}
		File[] sourceFiles = new File(policyNaxxletFolder).listFiles(new FilenameFilter() {
			public boolean accept(File dir, String name) {
				return name.endsWith(".java") && name.startsWith("Condition");
			}
		});
		logger.info("Preparing '" + sourceFiles.length + "' source files for compilation");

		this.compiler.compile(FileSystemUtil.filesToStrings(sourceFiles));
		
		logger.info("Policy file '" + policyFileName + "' successfully processed");
	}

	private void validateAgainstSchema(String policyFileName) throws Exception {
		File policyFile = new File(policyFileName);
		Assert.assertFileExistsAndReadable(policyFile);
		this.schemaValidator.validate(policyFile);
	}
}
